﻿"""Return the factorial of n, an exact integer >= 0.

    >>> [factorial(n) for n in range(6)]
    [1, 1, 2, 6, 24, 120]
    >>> factorial(30)
    265252859812191058636308480000000
    >>> factorial(-1)
    Traceback (most recent call last):
        ...
    ValueError: n must be >= 0

    Factorials of floats are OK, but the float must be an exact integer:
	
    >>> factorial(30.1)
    Traceback (most recent call last):
        ...
    ValueError: n must be exact integer
    >>> factorial(30.0)
    265252859812191058636308480000000

    It must also not be ridiculously large:
	
    >>> factorial(1e100)
    Traceback (most recent call last):
        ...
    OverflowError: n too large
"""


import os
import sys
import math

import pygame
#from pygame import surfarray



## 一個類別用以敘述二維座標中的「點」。
#  
#  內建四則運算、比較、捨入。
class Point():
	""" hahahah """
	__slots__ = ["x", "y"]
	def __init__(self, x=0, y=0):
		self.x = x
		self.y = y
		
	def __add__(self, rp1):
		p = Point()
		p.x = self.x + rp1.x
		p.y = self.y + rp1.y
		return p
		
	def __sub__(self, rp1):
		p = Point()
		p.x = self.x - rp1.x
		p.y = self.y - rp1.y
		return p
		
	def __mul__(self, rd1):
		p = Point()
		p.x = self.x * rd1
		p.y = self.y * rd1
		return p
		
	def __floordiv__(self, rd1):
		p = Point()
		p.x = self.x // rd1
		p.y = self.y // rd1
		return p
		
	def __truediv__(self, rd1):
		p = Point()
		p.x = self.x / rd1
		p.y = self.y / rd1
		return p
		
		
	def __rmul__(self, ld1):
		p = Point()
		p.x = self.x * ld1
		p.y = self.y * ld1
		return p
		
	def __rfloordiv__(self, ld1):
		p = Point()
		p.x = self.x // ld1
		p.y = self.y // ld1
		return p
		
	def __rtruediv__(self, ld1):
		p = Point()
		p.x = self.x / ld1
		p.y = self.y / ld1
		return p
		
	def __neg__(self):
		p = Point()
		p.x = -self.x
		p.y = -self.y
		return p
		
	def __round__(self, n=0):
		p = Point()
		p.x = round(self.x, n)
		p.y = round(self.y, n)
		return p
	
	def __eq__(self, rp1):
		return self.x == rp1.x and self.y == rp1.y
		
	def __str__(self):
		return "[x = " + str(self.x) + "][y = " + str(self.y) + "]"
		
	def MoveTo(self, p):
		self.x = p.x
		self.y = p.y
		
	def Pos(self):
		return self
		
## 一個類別用以敘述矩形邊界。
#  
#  
class Rect():
	__slots__ = ["x", "y", "width", "height"]
	def __init__(self, x=0, y=0, width=0, height=0):
		self.x = x
		self.y = y
		self.width = width
		self.height = height
		
		
	def MoveTo(self, p):
		self.x = p.x
		self.y = p.y
		
	def __str__(self):
		return "[x = " + str(self.x) + "][y = " + str(self.y) + "]" + \
				"[width = " + str(self.width) + "][height = " + str(self.height) + "]"
		
		
## 計算任意物件的距離。
#  
#  
def MakeRectBy2Point(p1, p2):
	bx = max(p1.x, p2.x)
	sx = min(p1.x, p2.x)
	by = max(p1.y, p2.y)
	sy = min(p1.y, p2.y)
	return Rect(sx, sy, bx - sx, by - sy)
	
## 計算任意物件的距離。
#  
#  
def Distance(p1, p2):
	p1 = p1.Pos()
	p2 = p2.Pos()
	return math.sqrt((p2.x - p1.x)**2 + (p2.y - p1.y)**2)

# rotate by original
def PointRotate(p, angle, o=Point()):
	radians = math.radians(angle)
	x = p.x - o.x
	y = p.y - o.y
	y = -y
	
	x1=math.cos(radians)*x-math.sin(radians)*y
	y1=math.cos(radians)*y+math.sin(radians)*x

	y1 = -y1
	x = x1 + o.x
	y = y1 + o.y

	return Point(x, y)
	
	
def PointInRotatedBoundingBox(p, w, h, angle):
	p = PointRotate(p, angle)
	if angle >= 0 and angle <=90:
		rp = PointRotate(Point(w, 0), angle)
		x = p.x
		y = p.y - rp.y
	if angle > 90 and angle <=180:
		rp = PointRotate(Point(w, 0), angle)
		x = p.x - rp.x
		rp = PointRotate(Point(w, h), angle)
		y = p.y - rp.y
	if angle > 180 and angle <=270:
		rp = PointRotate(Point(w, h), angle)
		x = p.x - rp.x
		rp = PointRotate(Point(0, h), angle)
		y = p.y - rp.y
	if angle > 270 and angle <=360:
		rp = PointRotate(Point(0, h), angle)
		x = p.x - rp.x
		y = p.y
		
	#y = -y
	#x = -x
	return round(Point(x,y),4)
	
	
def PointInFlipedBoundingBox(p, w, h, flipx, flipy):
	x = p.x
	y = p.y
	if flipx == True:
		x = w - p.x
	if flipy == True:
		y = h - p.y
	return Point(x,y)
		
def PointInScaleRateBoundingBox(p, w, h, scale_ratex, scale_ratey):
	#w = scale.x * surface.get_width()
	#h = scale.y * surface.get_height()
	#scale = (w, h)
	x = p.x * scale_ratex
	y = p.y * scale_ratey
	return Point(x, y)
	
def PointInScaleFixedBoundingBox(p, w, h, scale_fixedx, scale_fixedy):
	scale_ratex = float(scale_fixedx) / w
	scale_ratey = float(scale_fixedy) / h
	x = p.x * scale_ratex
	y = p.y * scale_ratey
	return Point(x, y)
	
def PointInClipedBoundingBox(p, w, h, rect):
	#w = scale.x * surface.get_width()
	#h = scale.y * surface.get_height()
	#scale = (w, h)
	x = p.x - rect.x
	y = p.y - rect.y
	return Point(x, y)
	
BLEND_COPY = 0
BLEND_ADD = pygame.BLEND_ADD
BLEND_SUB = pygame.BLEND_SUB
BLEND_MUL = pygame.BLEND_MULT
BLEND_MIN = pygame.BLEND_MIN
BLEND_MAX = pygame.BLEND_MAX

TRANSPOSE_FORWARD = 90
TRANSPOSE_BACKWARD = -90
TRANSPOSE_OPPOSITE = 180


#channel
CHANNEL_NAME = {
	"R":0,
	"G":1,
	"B":2,
	"A":3,
	"L":0,
	"1":0
}


#-----GraphEngine-----
class PygameGraphOperator():
	@staticmethod
	def _format2depth_(format):
		depth = 0
		if format == "RGB":
			depth = 24
		if format == "RGBA":
			depth = 32
		if format == "L":
			depth = 8
		return depth
	
	#---基本系列---
	@staticmethod
	def create(size, format):
		if format != "RGBA":
			return pygame.Surface((size[0], size[1]), 0, PygameGraphOperator._format2depth_(format))
		else:
			return pygame.Surface((size[0], size[1]), pygame.SRCALPHA, 32)
	@staticmethod
	def copy(surface):
		return surface.copy()
		
	#def fill(surface, color):
	#	return 
		
	@staticmethod
	def convert(surface, format):
		return surface.convert(PygameGraphOperator._format2depth_(format))
		
	@staticmethod
	def issame(surface1, surface2):
		if not (suf1.get_width() == suf2.get_width() and suf1.get_height() == suf2.get_height()):
			return False
			
		rgbaarray1 = surfarray.pixels3d(surface1)
		rgbaarray2 = surfarray.pixels3d(surface2)
		
		if rgbaarray1.shape != rgbaarray2.shape:
			return False
			
		for x in range(suf1.get_width()):
			for y in range(suf1.get_height()):
				c = rgbaarray1[x,y] - rgbaarray2[x,y]
				if (c > 0).any() or (c < 0).any():
					return False
					
		return True

	
	#灰階
	@staticmethod
	def grayscale(surface):
		monosurface = PygameGraphOperator.create((surface.get_width(), surface.get_height()), "RGBA")
		rgbaarray = surfarray.pixels3d(surface)
		monoaarray = surfarray.pixels3d(monosurface)
		monoalphaarray = surfarray.pixels_alpha(monosurface)
		
		monoaarray[:,:,0] = rgbaarray[:,:,0] // 3 + rgbaarray[:,:,1] // 3 + rgbaarray[:,:,2] // 3
		monoaarray[:,:,1] = monoaarray[:,:,0]
		monoaarray[:,:,2] = monoaarray[:,:,0]
		monoalphaarray[:,:] = 255

		return monosurface
	
	
	#超過最大值255會自動歸0
	@staticmethod
	def channelenhance(surface, rgbCuple):
		r, g, b = rgbCuple
		resultSurface = PygameGraphOperator.copy(surface)
		rbgarray = surfarray.pixels3d(resultSurface)

		rbgarray[:,:,0] = rbgarray[:,:,0] * r
		rbgarray[:,:,1] = rbgarray[:,:,1] * g
		rbgarray[:,:,2] = rbgarray[:,:,2] * b

		return resultSurface
		
	#色板分離
	"""
	@staticmethod
	def channel_split(surface, Channel):
		mono_surface = PygameGraphOperator.create((surface.get_width(), surface.get_height()), "L")
		rgbaarray = surfarray.pixels3d(surface)
		mono_rgbaarray = surfarray.pixels3d(mono_surface)
		mono_rgbaarray[:,:,0] = rgbaarray[:,:,CHANNEL_NAME[Channel]]
	"""
	
	#---貼圖系列---
	#numpy支援
	@staticmethod
	def channelpaste(dstSurface, srcSurface, dstChannel, srcChannel):
		if dstChannel != "A"	and srcChannel != "A":
			dst_rgb_array = surfarray.pixels3d(dstSurface)
			src_rgb_array = surfarray.pixels3d(srcSurface)
			dst_rgb_array[:,:, CHANNEL_NAME[dstChannel] ] = src_rgb_array[:,:, CHANNEL_NAME[srcChannel]]
		elif dstChannel == "A"	and srcChannel != "A":
			dst_alpha_array = surfarray.pixels_alpha(dstSurface)
			src_rgb_array = surfarray.pixels3d(srcSurface)
			dst_alpha_array[:,:] = src_rgb_array[:,:, CHANNEL_NAME[srcChannel]]
			#非常重要 此處有更新
			del dst_alpha_array
		elif dstChannel != "A"	and srcChannel == "A":
			dst_rgb_array = surfarray.pixels3d(dstSurface)
			src_alpha_array = surfarray.pixels_alpha(srcSurface)
			dst_rgb_array[:,:, CHANNEL_NAME[dstChannel]] = src_alpha_array[:,:]
			#非常重要 此處有更新
			del src_alpha_array
		elif dstChannel == "A"	and srcChannel == "A":
			dst_alpha_array = surfarray.pixels_alpha(dstSurface)
			src_alpha_array = surfarray.pixels_alpha(srcSurface)
			dst_rgb_array[:,:] = src_alpha_array[:,:]
			#非常重要
			del dst_alpha_array
			del src_alpha_array
	
	@staticmethod
	def setalphavalue(surface, alpha):
		dst_alpha_array = surfarray.pixels_alpha(surface)
		dst_alpha_array[:,:] = alpha
		#非常重要
		del dst_alpha_array
		
	@staticmethod
	def mulalphavalue(surface, alpha):
		dst_alpha_array = surfarray.pixels_alpha(surface)
		alpha2 = (alpha / 255.0)
		dst_alpha_array[:,:] = dst_alpha_array[:,:] * alpha2
		#非常重要
		del dst_alpha_array
		
		
	@staticmethod
	def blend(dstSurface, srcSurface, pos, blendMode=BLEND_COPY, alpha=None):
		if alpha == None:
			dstSurface.blit(srcSurface, (pos[0], pos[1]), None, blendMode)
		else:
			tempSurface = srcSurface.convert_alpha()
			PygameGraphOperator.mulalphavalue(tempSurface, alpha)
			dstSurface.blit(tempSurface, (pos[0], pos[1]), None, blendMode)
		return
	
	#濾鏡系列
	@staticmethod
	def laplacian(surface):
		return pygame.transform.laplacian(surface)
	
	
	#反向有問題，alpha色板可能會消失
	@staticmethod
	def invert(surface):
		resultsurface = PygameGraphOperator.copy(surface)
		surfacearray = surfarray.pixels3d(resultsurface)
		#numpy.negative(surfacearray[:,:,:3] , surfacearray[:,:,:3])
		surfacearray[:,:,0] = 255 - surfacearray[:,:,0]
		surfacearray[:,:,1] = 255 - surfacearray[:,:,1]
		surfacearray[:,:,2] = 255 - surfacearray[:,:,2]
		return  resultsurface
		
	'''
	@staticmethod
	def blur(surface):
		image = PygameGraphOperator._convertPygame2PIL_(surface)
		image = image.filter(ImageFilter.BLUR)
		return  PygameGraphOperator._convertPIL2Pygame_(image)
	
	@staticmethod
	def smooth(surface):
		image = PygameGraphOperator._convertPygame2PIL_(surface)
		image = image.filter(ImageFilter.SMOOTH)
		return  PygameGraphOperator._convertPIL2Pygame_(image)
	'''
	
	#簡易變形系列
	"""
	@staticmethod
	def scroll( surface, offsetX, offsetY):
		resultSurface = PygameGraphOperator.copy(surface)
		resultSurface.scroll(offsetX, offsetY)
		
		return resultSurface
	
		
	@staticmethod
	def point_scroll( surface, offsetX, offsetY, point):
		x = point.x + offsetX
		if x > surface.get_width():
			x = x - surface.get_width()
			
		y = point.y + offsetY
		if y > surface.get_height():
			y = y - surface.get_height()
		return (x,y)
	"""
	
	@staticmethod
	def clip(surface, rect):
		resultSurface = surface.subsurface((rect.x, rect.y, rect.width, rect.height))
		return resultSurface.copy()
		
	@staticmethod
	def flip(surface, flipx, flipy):
		return pygame.transform.flip(surface, flipx, flipy)


	#兩軸比例縮放
	@staticmethod
	def scale_rate(surface, scale_ratex, scale_ratey):
		w = scale_ratex * surface.get_width()
		h = scale_ratey * surface.get_height()
		_scale = (int(w), int(h))
		return pygame.transform.smoothscale(surface, _scale)

	def scale_fixed(surface, scale_fixedx, scale_fixedy):
		_scale = (int(scale_fixedx), int(scale_fixedy))
		return pygame.transform.smoothscale(surface, _scale)

	@staticmethod
	def rotate(surface, angle):
		return pygame.transform.rotate(surface, angle)
	
	#垂直旋轉
	def transpose(surface, pose):
		if pose == TRANSPOSE_FORWARD:
			return pygame.transform.rotate(surface, 90)
		elif pose == TRANSPOSE_BACKWARD:
			return pygame.transform.rotate(surface, 270)
		elif pose == TRANSPOSE_OPPOSITE:
			return pygame.transform.rotate(surface, 180)
		else:
			raise TypeError("Most be TRANSPOSE_*")
			
	@staticmethod
	def colorshape(surface, color):
		alphasurface = GraphOperator.create(surface.get_size(), "RGBA")
		alphasurface.fill(color)
		GraphOperator.channelpaste(alphasurface, surface,"A", "R")
		return alphasurface
	
GraphOperator = PygameGraphOperator
	

		
class Barn():
	def __init__(self):
		self.library = {}
		self.path = ""
		
	def fetch(self, key):
		if key in self.library:
			return self.library[key]
		else:
		
			try:
				surface = pygame.image.load(os.path.join(self.path, key + ".bmp"))
				self.library[key] = surface
				return surface
			except pygame.error:
				pass
				
			try:
				sound = pygame.mixer.Sound(os.path.join(self.path, key + ".wav"))
				self.library[key] = sound
				return sound
			except pygame.error:
				pass
				
			return None
				
#class Win32Graph():
class PygameGraph():
	def __init__(self, w, h):
		pygame.init()
		self.size = self.width, self.height = w, h
		self.screen = pygame.display.set_mode(self.size) #pygame.OPENGL
		
		
	def draw_back(self):
		pygame.display.flip()
		#self.screen.fill((0,0,0))
		
		#這樣才可以移動跟關閉視窗，估計是使用windows的event系統
		for event in pygame.event.get():
			if event.type == pygame.QUIT:
				sys.exit()
				
		
	def get_back_surface(self):
		return self.screen
		
	
class PygameSound():
	def __init__(self):
		pygame.mixer.init(frequency=44100, size=-16, channels=2, buffer=512)
		pygame.mixer.pre_ini(frequency=44100, size=-16, channels=2, buffer=512)
		self.queue = []
		self.nowframe = 0
		
	def addsound(self, sound, channel_limit=1):
		self.queue.append((sound, channel_limit, self.nowframe))
		if len(self.queue) > 30:
			self.queue.pop(0)
			
	def playback(self, nowframe):
		self.nowframe = nowframe
		for i in range(pygame.mixer.get_num_channels - 1):
			chn = pygame.mixer.Channel(id)
			if not chn.get_busy():
				for j in len(self.queue):
					sound, channel_limit, frame = self.queue[j]
					if channel_limit > i and nowframe - frame < 5:
						chn.play(sound)
						break
				chn.pop(j)

		
#-----GraphEngine-----
	

#將來要實驗surface.blit到底會不會繪製alpha
#結果是不會繪製alpha


#font = pygame.font.Font("mingliu.ttf",8)
#font = pygame.font.Font(u"新細明體".encode("big5"),8)
#font = pygame.font.SysFont(u"新細明體".encode("big5"),20)
#font.set_bold(True)
#font = pygame.font.Font(os.environ['SYSTEMROOT'] + "\\Fonts\\mingliu.ttc", size)
#font = pygame.font.Font(os.environ['SYSTEMROOT'] + "\\Fonts\\tahoma.ttf", size)


#Font and String
class Font():
	def __init__(self, size=12, color=(128, 128, 128), antialias=False, wordspace=0, linespace=0.5):
		self.size = size
		#之後color也可以用surface
		self.color = (128, 128, 128)
		self.font_name = pygame.font.match_font("細明體新細明體")
		#self.font_name = pygame.font.match_font("Fixedsys")
		self.py_font = pygame.font.Font(self.font_name, self.size)
		self.antialias = antialias
		self.word_spacing = wordspace
		
		#倍率
		self.line_spacing_factor = linespace
		self.line_spacing = int(self.line_spacing_factor * self.py_font.get_linesize())
		
	def GetLineWidth(self, message):
		advence = 0
		for i in range(len(message)):
			advence += self.py_font.metrics(message[i])[0][4] + self.word_spacing
			
		#剪掉右邊的空白
		advence -= self.word_spacing
		return advence
		
	def CreateTextSurface(self, message):
		alphaSurface = self._MonoSurfaceOneLine(message)
		surface = PygameGraphOperator.colorshape(alphaSurface, self.color)
		return surface
		
	def CreateMultilineSurface(self, message, linewidth):
		alphaSurface = self._MonoSurfaceMultiLine(message, linewidth)
		surface = PygameGraphOperator.colorshape(alphaSurface, self.color)
		return surface
		
	
	def _MonoSurfaceOneLine(self, message):
		width = self.GetLineWidth(message)
		height = self.py_font.get_height()
		surface = pygame.Surface((width, height))
		
		#metricses = font.metrics(message)
		advence = 0
		for i in range(len(message)):
			metricses_i = self.py_font.metrics(message[i])[0][4]
			if ord(message[i]) < 128:
				metricses_i = metricses_i / 2
				
			surface.blit(self.py_font.render(message[i], self.antialias,(255, 255, 255), (0,0,0)),
						 (advence, 0))
			advence = advence + metricses_i + self.word_spacing
		#當self.word_spacing = 0的快速方法
		#surface = self.py_font.render(message, self.antialias, (255, 255, 255), (0,0,0)).convert()
		return surface
		
	def _MonoSurfaceMultiLine_List(self, message, linewidth):
		surface_list = []
		advence = 0
		last_pos = 0
		metricses = self.py_font.metrics(message)
		for i in range(len(metricses)):
			#print(unicodedata.category(message[i]))
			#print(ord(message[i]))
			metricses_i = metricses[i][4]
			if ord(message[i]) < 128:
				metricses_i = metricses_i / 2
				
			pre_advence = advence + metricses_i + self.word_spacing
			if pre_advence >= linewidth or message[i] == "\n":
				surface_list.append(self._MonoSurfaceOneLine(message[last_pos:i]))
				last_pos = i
				advence = 0
				
			if message[i] != "\n":
				advence += metricses_i + self.word_spacing
			else:
				last_pos = i + 1
				
		surface_list.append(self._MonoSurfaceOneLine(message[last_pos:len(metricses)]))	
		
		return surface_list
		
	def _MonoSurfaceMultiLine(self, message, linewidth):
		surface_list = self._MonoSurfaceMultiLine_List(message, linewidth)
		width = linewidth
		height = 0
		for suf in surface_list:
			height += suf.get_height() + self.line_spacing
		
		#剪掉下面的空白		
		height -= self.line_spacing
			
		surface = pygame.Surface((width, height))
		
		advence = 0
		for suf in surface_list:
			surface.blit( suf, (0, advence))
			advence += suf.get_height() + self.line_spacing
			
		return surface
		
	

		
class GameNode():
	def __init__(self, pos=Point(0,0), surface = None, centerpos=Point(0,0), name="someone"):
		self.name = name
		self.childs = []
		self.pos = pos
		self.zdepth = 0
		self.dstsurface = None
		
		self.centerpos = centerpos
		self.surface = surface
		
		#如果這樣設定則不顯示圖片
		#self.isvisible = False
		
		#Debug線寬
		#self.border = 1
		#self.bordercolor = RGB(128,128,128)
		#self.showname = False
		return
	
	def moveTo(self, pos):
		self.pos = pos
		
	def getPos(self):
		return self.pos
		

	def advance(self):
		return

	
'''
class RootRenderNode(RenderNode):
	def __init__(self, dst_surface_):
		RenderNode.__init__(self)
		self.renderState.hasSrcRenderRequest = False
		self.renderState.hasDstSurface = True
		
		#要設定為最上層的surface
		self.dstsurface = dst_surface_
'''

		
		
class Transform():
	def __init__(self):
		self.blendmode = BLEND_COPY
		
		#self.clip = None		#self.clip = (0,0,0,0)
		#self.scale_rate = None		#self.scale  = (0,0)
		#self.scale_fixed = None		#self.scale  = (0,0)
		#self.rotate = None		#self.rotate  = 0
		#self.flip = None		#self.flip = (False,False)
		#self.alpha = None		#self.alpha  = 0

		
#將TransformRanderRequest轉換成RanderRequeest 隨時可以繪製到另一個surface的狀態
def TransformSurface(node):	
	if "transform" in node.__dict__ and node.transform != None:
		transform = node.transform
	else:
		return (node.surface, (node.centerpos.x, node.centerpos.y), BLEND_COPY, None)
	
	
	temp_surface = node.surface
	temp_cp = node.centerpos
	
	if "clip" in transform.__dict__  and  transform.clip != None:
		width = temp_surface.get_width()
		height = temp_surface.get_width()
		temp_cp = PointInClipedBoundingBox(temp_cp, width, height, transform.clip)
		temp_surface = PygameGraphOperator.clip(temp_surface, transform.clip)
		
	if "scale_rate" in transform.__dict__  and  transform.scale_rate != None:
		width = temp_surface.get_width()
		height = temp_surface.get_width()
		scale_ratex = transform.scale_rate[0]
		scale_ratey = transform.scale_rate[1]
		temp_cp = PointInScaleRateBoundingBox(temp_cp, width, height, scale_ratex, scale_ratey)
		temp_surface = PygameGraphOperator.scale_rate(temp_surface, scale_ratex, scale_ratey)
		
	if "scale_fixed" in transform.__dict__  and  transform.scale_fixed != None:
		width = temp_surface.get_width()
		height = temp_surface.get_width()
		scale_fixedx = transform.scale_fixed[0]
		scale_fixedy = transform.scale_fixed[1]
		temp_cp = PointInScaleFixedBoundingBox(temp_cp, width, height, scale_fixedx, scale_fixedy)
		temp_surface = PygameGraphOperator.scale_fixed(temp_surface, scale_fixedx, scale_fixedy)
		
	if "rotate" in transform.__dict__  and  transform.rotate != None:
		width = temp_surface.get_width()
		height = temp_surface.get_width()
		temp_cp = PointInRotatedBoundingBox(temp_cp, width, height, transform.rotate)
		temp_surface = PygameGraphOperator.rotate(temp_surface, transform.rotate)
		
	if "flip" in transform.__dict__  and  transform.flip != None:
		width = temp_surface.get_width()
		height = temp_surface.get_width()
		flipx = transform.flip[0]
		flipy = transform.flip[1]
		temp_cp = PointInFlipedBoundingBox(temp_cp, width, height, flipx, flipy)
		temp_surface = PygameGraphOperator.flip(temp_surface, flipx, flipy)
		
	if "alpha" in transform.__dict__  and transform.alpha != None:
		alpha = transform.alpha
	else:
		alpha = None
		
	
	if "blendmode" in transform.__dict__  and transform.blendmode != None:
		blendmode = transform.blendmode 
	else:
		blendmode = BLEND_COPY
	
	return temp_surface, (temp_cp.x, temp_cp.y), blendmode, alpha
	

class RenderNodeCombiner():
	def __init__(self):
		#此list裡面儲存著tuple( dst_surface, src_request, point, blend_mode)
		self.blendqueue = []
		
	def clear(self):
		self.blendqueue = []
		
	def extend(self, rootnode):
		stack = [(rootnode, (0,0), rootnode.dstsurface)]
		while(len(stack) != 0):
			lastnode , (father_posx, father_posy), father_dst_surface= stack[len(stack) - 1]
			stack.pop()
			

			if "invisible" in lastnode.__dict__ and lastnode.invisible:
				continue
				
			#if renderState.isLocation == True:
			father_posx += lastnode.pos.x
			father_posy += lastnode.pos.y
				
			if "dstsurface" in lastnode.__dict__ and lastnode.dstsurface:
				father_dst_surface = lastnode.dstsurface
			
			if "surface" in lastnode.__dict__ and lastnode.surface:
				surface, (cpx, cpy), blendmode, alpha = TransformSurface(lastnode)
				px = father_posx - cpx
				py = father_posy - cpy
				
				self.blendqueue.append( (father_dst_surface, surface, (px,py), blendmode, alpha) )
							
			if "childs" in lastnode.__dict__ and lastnode.childs:
				for child in iter(lastnode.childs):
					stack.append((child, (father_posx, father_posy), father_dst_surface))
			
		
	def blendall(self):
		for i in range(len(self.blendqueue)):
			cpnode = self.blendqueue[i]
			PygameGraphOperator.blend(cpnode[0], cpnode[1], cpnode[2], cpnode[3], cpnode[4])

		
class SimpleClientLoop():
	def __init__(self):
		self.graph_combiner = RenderNodeCombiner()
		self.graph_loop = threadLoop.ThreadLoop(15, self.loop)
		self.graph_pygame = PygameGraph(1024,768)
		self.root_node = RootRenderNode(self.graph_pygame.get_back_surface())
		
	def loop(self):
		if self.root_node != None: 
			self.graph_pygame.get_back_surface().fill((0,0,0))
			self.graph_combiner.clear()
			self.graph_combiner.extend(self.root_node)
			self.graph_combiner.blend_all()
			
		#.flip()
		self.graph_pygame.draw_back()
		
	def getThreadLoop(self):
		return self.graph_loop
		
	def oneLoop(self):
		self.getThreadLoop().one_loop()
		
	def detach(self):
		self.getThreadLoop().detach()
			
	def getRootNode(self):
		return self.root_node


class ClientLoop():
	def __init__(self, gameobject, screen_width=1024, screen_height=768, fps=60):
		self.graph_combiner = RenderNodeCombiner()
		self.graph_pygame = PygameGraph(screen_width, screen_height)
		
		if(fps < 0):
			raise ValueError("In thread_loop_base::set_interval_ms() interval_ms < 0")
		self._interval_ms = 1000.0 / fps
		self.frameconut = 0
		self.clock = pygame.time.Clock()
		self.deque_wait_ms = []
		self.fps = 0
		
		self.gameobject = gameobject
		self.km_state = {}
		#self.count = 0
		
	def _kmloop(self):
		self.km_state["key-state"] = pygame.key.get_pressed()
		p = pygame.mouse.get_pos()
		
		self.km_state["mouse-pos"] = Point(p[0], p[1])
		
		mp = pygame.mouse.get_pressed()	
		if mp[2] == 1:
			right = True
		else:
			right = False
			
		if mp.x == 1:
			left = True
		else:
			left = False
			
		self.km_state["mousedown-left"] = left
		self.km_state["mousedown-right"] = right
		
	def SetFps(self, fps):
		self._interval_ms = 1000.0 / fps

	def GetFps(self):
		return self.graph_loop.get_fps()
		
	def loop(self):
		#if self.root_node != None: 
		#self.graph_pygame.get_back_surface().fill((0,0,0))
		
		self.frameconut = self.frameconut + 1
		self.clock.tick(1000.0 / self._interval_ms)
		
		self.deque_wait_ms.append(self.get_wait_ms())
		
		if( len(self.deque_wait_ms) > 120) :
			self.deque_wait_ms.pop(0)

		sum = 1;
		for wm in self.deque_wait_ms:
			sum += wm

		self.fps = 1000.0 / ( 1.0 * sum / len(self.deque_wait_ms))
		#self.count = self.count + 1
		#if(self.count == 60):
		#	self.count = 0;
		#	print(self.fps)
			
		
		self.km_adv.loop()
		self.game_instance.kmEvent(self.km_adv.getKmState())

		
		
		self.game_instance.advance()
		
		#self.game_instance.prepare()
		
		self.graph_combiner.clear()
		node = self.game_instance.getDrawNode()
		node.dstSurface = self.graph_pygame.get_back_surface()
		self.graph_combiner.extend(node)
		self.graph_combiner.blend_all()
		
		self.graph_pygame.draw_back()
		
		#self.game_instance.cleanup()
		
	def oneLoop(self):
		self.getThreadLoop().one_loop()

import ctypes
lilithapi = ctypes.CDLL("lilithapi.dll")

class TestMgr():
	def __init__(self):
		#self.successcount = 0
		#self.failcount = 0
		self.passlist = []
		self.faillist = []
		
	def TestItem(self, func):
		lilithapi.timer_start()
		issuccess = func()
		lilithapi.timer_print()
		name = func.__name__
		if issuccess:
			self.passlist.append(name)
			print("[OK]   %s" %(name))
		else:
			self.faillist.append(name)
			print("[Fail] %s" %(name))
		
def TestOprImage(name, surface, mothed, *pm):
		if len(pm) == 0:
			def funcObj():
				return mothed(surface)
		elif len(pm) == 1:
			def funcObj():
				return mothed(surface, pm[0])
		elif len(pm) == 2:
			def funcObj():
				return mothed(surface, pm[0], pm[1])
		
		lilithapi.timer_start()
		savesurface = funcObj()
		lilithapi.timer_print()
		print("[OPR]  %s" %(name))

		
		#print(name + " need : \t\t" + str(performance.get_interval() ))
		#print(name + " avg : \t\t" + str(performanceLoop.get_avg() ))
		pygame.image.save(savesurface, name + ".bmp")
		
def TestOpr2Image(name, surface1, surface2, mothed, *pm):
		dstsurface = PygameGraphOperator.copy(surface2)
		srcsurface = surface1
		if len(pm) == 0:
			def funcObj():
				return mothed(dstsurface, srcsurface)
		elif len(pm) == 1:
			def funcObj():
				return mothed(dstsurface, srcsurface, pm[0])
		elif len(pm) == 2:
			def funcObj():
				return mothed(dstsurface, srcsurface, pm[0], pm[1])
		elif len(pm) == 3:
			def funcObj():
				return mothed(dstsurface, srcsurface, pm[0], pm[1], pm[2])
		elif len(pm) == 4:
			def funcObj():
				return mothed(dstsurface, srcsurface, pm[0], pm[1], pm[2], pm[3])
				
		lilithapi.timer_start()
		funcObj()
		lilithapi.timer_print()
		print("[OPR]  %s" %(name))

		
		#print(name + " need : \t\t" + str(performance.get_interval() ))
		#print(name + " avg : \t\t" + str(performanceLoop.get_avg() ))
		pygame.image.save(dstsurface, name + ".bmp")
		
def TestGameNode(name, surface1, node):
	dstsurface = PygameGraphOperator.copy(surface1)
	combiner = RenderNodeCombiner()
	rootnode = GameNode()
	rootnode.dstsurface = dstsurface
	rootnode.childs.append(node)
	combiner.extend(rootnode)
	combiner.blendall()
	pygame.image.save(dstsurface, name + ".bmp")
	
def createimage(surface):
	TestOprImage("copy", surface,  PygameGraphOperator.copy)
	TestOprImage("clip", surface,  PygameGraphOperator.clip, Rect(10,10,60,60))
	TestOprImage("convert", surface,  PygameGraphOperator.convert, "RGB")
	TestOprImage("grayscale", surface,  PygameGraphOperator.grayscale)
	TestOprImage("laplacian", surface,  PygameGraphOperator.laplacian)
	TestOprImage("invert", surface,  PygameGraphOperator.invert)
	#TestOprImage("blur", surface,  PygameGraphOperator.blur)
	#TestOprImage("smooth", surface,  PygameGraphOperator.smooth)
	TestOprImage("flip", surface,  PygameGraphOperator.flip, False, True)
	TestOprImage("scale_rate", surface,  PygameGraphOperator.scale_rate, 1.5, 1)
	TestOprImage("scale_fixed", surface,  PygameGraphOperator.scale_fixed, 100, 100)
	TestOprImage("rotate", surface,  PygameGraphOperator.rotate, 30)
	TestOprImage("channelenhance", surface,  PygameGraphOperator.channelenhance, (0.4,1,0.85))
	TestOprImage("colorshape", surface, PygameGraphOperator.colorshape, (255,0,128))
	
	font = Font()
	textsurface = font.CreateTextSurface("haha lala猜猜我是誰?")
	TestOprImage("font_oneline", textsurface,  PygameGraphOperator.copy)
	
	textsurface = font.CreateMultilineSurface("haha lala猜猜我是誰?", 20)
	TestOprImage("font_multiline", textsurface,  PygameGraphOperator.copy)
	
#surface3需要與surface1同尺寸
def createimage2(surface1, surface2, surface3):
	TestOpr2Image("blend", surface1, surface2, PygameGraphOperator.blend, (64,64))
	TestOpr2Image("blendadd", surface1, surface2, PygameGraphOperator.blend, (64,64), BLEND_ADD)
	TestOpr2Image("blendsub", surface1, surface2, PygameGraphOperator.blend, (64,64), BLEND_SUB)
	TestOpr2Image("blendmul", surface1, surface2, PygameGraphOperator.blend, (64,64), BLEND_MUL)
	TestOpr2Image("blendmin", surface1, surface2, PygameGraphOperator.blend, (64,64), BLEND_MIN)
	TestOpr2Image("blendmax", surface1, surface2, PygameGraphOperator.blend, (64,64), BLEND_MAX)
	srcsurface = PygameGraphOperator.copy(surface1)
	PygameGraphOperator.mulalphavalue(srcsurface, 128)
	TestOpr2Image("blendtrans", srcsurface, surface2, PygameGraphOperator.blend, (64,64))
	
	srcsurface = PygameGraphOperator.copy(surface1)
	PygameGraphOperator.setalphavalue(srcsurface, 128)
	TestOpr2Image("blendconstalpha", srcsurface, surface2, PygameGraphOperator.blend, (64,64))
		
	#channel_paste必須同尺寸
	TestOpr2Image("channelpaste", surface1, surface3, PygameGraphOperator.channelpaste, "R", "R")
	
def createimage3(surface1,surface2):
	node = GameNode(pos=Point(32,32), surface=surface1)
	TestGameNode("simplenode", surface2, node)
	
	node1 = GameNode(pos=Point(32,32))
	node2 = GameNode(pos=Point(50,50), surface=surface1)
	node3 = GameNode(pos=Point(100,100), surface=surface1)
	node1.childs.append(node2)
	node1.childs.append(node3)
	TestGameNode("morenode", surface2, node1)
	
	node = GameNode(pos=Point(0,0), surface=surface1, centerpos=Point(64,64))
	node.transform = Transform()
	node.transform.alpha = 128
	node.transform.scale_rate = (2,2)
	node.transform.rotate = 45
	node.transform.blendmode = BLEND_SUB
	#node.transform.blendmode = pygame.BLEND_RGBA_SUB
	TestGameNode("transnode", surface2, node)
	
	
def pointtest(tester):
	def PointEqual():
		return Point(5,13) == Point(5,13) and Point(5,13) != Point(5,-13)
		
	def PointAdd():
		return (Point(10,10) + Point(-5,3)) == Point(5,13)
		
	def PointSub():
		return (Point(10,10) - Point(-5,3)) == Point(15,7)
		
	def PointMul():
		return (Point(10,10) * 3) == Point(30,30)
		
	def PointDiv():
		return (Point(12,9) / -10) == Point(-1.2,-0.9)
		
	def PointFloorDiv():
		return (Point(12,9) // -10) == Point(-2,-1)	
		
	def PointRMul():
		return (3 * Point(10,10)) == Point(30,30)
		
	def PointNeg():
		return -Point(10,10) == Point(-10,-10)
		
	def PointRound():
		return round(Point(21.87,12.34),1) == Point(21.9,12.3)

	def PointDistance():
		return Distance(Point(0,0), Point(3,4)) == 5
		
	def PointRotation():
		r1 = (PointRotate(Point(3,3),90) == Point(3, -3))
		r2 = (PointRotate(Point(3,3),90, Point(3,3)) == Point(3, 3))
		return (r1 and r2)
		
	def BoundingBoxRotate():
		r1 = PointInRotatedBoundingBox(Point(0,0),10,10,90) == Point(0,10)
		r2 = PointInRotatedBoundingBox(Point(0,0),10,10,270) == Point(10,0)
		r3 = round(PointInRotatedBoundingBox(Point(0,0),10,10,45),2) == \
			 round(Point(0,1.414 * 10 / 2),2)
		return (r1 and r2 and r3)
		
	def BoundingBoxFlip():
		r1 = PointInFlipedBoundingBox(Point(3,3),10,10,True,False) == Point(7,3)
		r2 = PointInFlipedBoundingBox(Point(3,3),10,10,True,True) == Point(7,7)
		return (r1 and r2)
		
	def BoundingBoxScaleRate():
		return PointInScaleRateBoundingBox(Point(3,3),10,10,2,2) == Point(6,6)
		
	def BoundingBoxScaleFixed():
		return PointInScaleFixedBoundingBox(Point(3,3),10,10,15,15) == Point(4.5,4.5)	
	
	def BoundingBoxClip():
		return PointInClipedBoundingBox(Point(0,1),10,10,Rect(3,3,3,3)) == Point(-3,-2)
	
	tester.TestItem(PointEqual)
	tester.TestItem(PointAdd)
	tester.TestItem(PointSub)
	tester.TestItem(PointMul)
	tester.TestItem(PointDiv)
	tester.TestItem(PointFloorDiv)
	tester.TestItem(PointRMul)
	tester.TestItem(PointNeg)
	tester.TestItem(PointRound)
	tester.TestItem(PointDistance)
	tester.TestItem(PointRotation)
	tester.TestItem(BoundingBoxRotate)
	tester.TestItem(BoundingBoxFlip)
	tester.TestItem(BoundingBoxScaleRate)
	tester.TestItem(BoundingBoxScaleFixed)
	tester.TestItem(BoundingBoxClip)
	
if __name__ == '__main__':
	tester = TestMgr()
	pygameGrahp = PygameGraph(800, 600)
	barn = Barn()
	
	def TestBarnFetchSurface():
		return str(barn.fetch("test128x128_32_1")) == "<Surface(128x128x32 SW)>"
		
	def TestBarnFetchSound():
		return "<Sound object at" in str(barn.fetch("close_door_2"))
		
	tester.TestItem(TestBarnFetchSurface)
	tester.TestItem(TestBarnFetchSound)
	#print(barn.fetch("test128x128_32_1").get_bytesize())
	
	#createimage(barn.fetch("test128x128_32_1"))
	#createimage2(barn.fetch("test128x128_32_1"), barn.fetch("test256x256_24"), barn.fetch("test128x128_32_2"))
	createimage3(barn.fetch("test128x128_32_1"), barn.fetch("test256x256_24"))
	pointtest(tester)
	